package edu.sysu.demo.rabbitmq;


import java.util.Random;
import java.util.stream.IntStream;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.math3.random.MersenneTwister;
import org.apache.commons.math3.random.RandomAdaptor;
import org.apache.commons.text.RandomStringGenerator;

public class RandomStringBuilder {

  private static final String INVALID_STATE_MESSAGE
      = "No character set/range specified,"
      + " invoke .useDigit()/.useUpperCase() etc."
      + " before invoking .build()";

  private static final String UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

  private static final String LOWERCASE = UPPERCASE.toLowerCase();

  private static final String DIGIT = "0123456789";

  private static final String SYMBOL = "!@#$%^&*()[]{};:,.?_+=-~";

  private static final String WHITESPACE;

  private final RandomStringGeneratorHolder randomStringGeneratorHolder;

  private StringBuilder candidates;

  private Random random;

  private RandomStringBuilder() {
    randomStringGeneratorHolder = new RandomStringGeneratorHolder() {
      private RandomStringGenerator randomStringGenerator;

      @Override
      public RandomStringGenerator get() {
        if (StringUtils.isEmpty(candidates)) {
          throw new IllegalStateException(INVALID_STATE_MESSAGE);
        }

        if (randomStringGenerator == null) {

          if (random == null) {
            random = new RandomAdaptor(new MersenneTwister());
          }

          this.randomStringGenerator = new RandomStringGenerator.Builder()
              .selectFrom(candidates.toString().toCharArray())
              .usingRandom(random::nextInt)
              .build();
        }
        return this.randomStringGenerator;
      }
    };
  }

  public static RandomStringBuilder newBuilder() {
    return new RandomStringBuilder();
  }

  public RandomStringBuilder useRandom(Random random) {
    this.random = random;
    return this;
  }

  public RandomStringBuilder useDigit() {
    ensureCandidate().candidates.append(DIGIT);
    return this;
  }

  public RandomStringBuilder useUpperCase() {
    ensureCandidate().candidates.append(UPPERCASE);
    return this;
  }

  public RandomStringBuilder useLowerCase() {
    ensureCandidate().candidates.append(LOWERCASE);
    return this;
  }

  public RandomStringBuilder useWhitespace() {
    ensureCandidate().candidates.append(WHITESPACE);
    return this;
  }

  public RandomStringBuilder useSymbol() {
    ensureCandidate().candidates.append(SYMBOL);
    return this;
  }

  public String build(int exactLength) {
    return randomStringGeneratorHolder.get().generate(exactLength);
  }

  public String build(int minLengthInclusive, int maxLengthInclusive) {
    return randomStringGeneratorHolder.get().generate(minLengthInclusive, maxLengthInclusive);
  }


  private RandomStringBuilder ensureCandidate() {
    if (candidates == null) {
      candidates = new StringBuilder();
    }
    return this;
  }

  private interface RandomStringGeneratorHolder {

    RandomStringGenerator get();
  }

  static {
    StringBuilder sb = new StringBuilder();
    IntStream.range(Character.MIN_CODE_POINT, Character.MAX_CODE_POINT)
        .filter(Character::isWhitespace)
        .forEach(x -> sb.append(Character.toChars(x)));

    WHITESPACE = sb.toString();
  }
}

